package alducente.media.sound {
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.media.ID3Info;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundLoaderContext;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	import flash.utils.getDefinitionByName;
	
	/**
	 * The <code>SoundDecorator</code> class wraps Flash's <code>Sound</code> object and consolidates all the methods and properties used to interact with sound.
	 * @author Carlo Alducente
	 * @version 1.0
	 * @see flash.media.Sound
	 */
	public class SoundDecorator {
	
		//CONSTANTS
		private static const DEFAULT_VOLUME:Number = 1;
		private static const DEFAULT_PAN:Number = 0;
		
		//PRIVATE VARS
		private var __sound:Sound;
		private var __channel:SoundChannel;
		private var __vol:Number;
		private var __pausePosition:Number;
		private var __isPaused:Boolean;
		private var __loops:int;
		private var __soundTransform:SoundTransform;
		private var __pan:Number;
		private var __isPlaying:Boolean;
		
		//PROTECTED VARS
		
		//PUBLIC VARS
		
		//CONSTRUCTOR
		/**
		 * Constructor. Creates a new <code>SoundDecorator</code> object.
		 * @param	linkage   Optional. The linkage name of the sound object if it exists in the library. Leaving this out will create a new instance of the <code>Sound</code> object for use.
		 **/
		public function SoundDecorator(linkage:String = null):void {
			__vol = DEFAULT_VOLUME;
			__pan = DEFAULT_PAN;
			__isPaused = false;
			__isPlaying = false;
			if(linkage){
				var soundDefinition:Class = getDefinitionByName(linkage) as Class;
				this.sound = new soundDefinition() as Sound;
			} else {
				this.sound = new Sound();
			}
			soundTransform = new SoundTransform();
		}
		
		//PRIVATE FUNCTIONS
		private function setVolume():void{
			if(channel){
				soundTransform.volume = this.vol;
				channel.soundTransform = soundTransform;
			} else {
				throw new Error("Can't change vol, channel doesn't exist.");
			}
		}
		
		private function setPan():void{
			if(channel){
				soundTransform.pan = this.pan;
				channel.soundTransform = soundTransform;
			} else {
				throw new Error("Can't pan audio, channel doesn't exist yet.");
			}
		}
		
		private function soundComplete(evt:Event):void{
			__loops--;
		}
		
		//PROTECTED FUNCTIONS
		
		//PUBLIC FUNCTIONS
		/**
		 * Use this method to load an external sound file.
		 * @param	url URL of the sound file.
		 * @param	buffer Amount of buffer in milliseconds before a loading sound can start playing.
		 * @see Sound.load()
		 */
		public function load(url:String, buffer:Number=1000):void{
			var request:URLRequest = new URLRequest(url);
			var context:SoundLoaderContext = new SoundLoaderContext(buffer);
			sound.load(request, context);
		}
		
		/**
		 * Plays the sound associated with the <code>SoundDecorator</code>.
		 * @param	startTime   The initial position in milliseconds at which playback should start.
		 * @param	loops   Defines the number of times a sound loops back to the startTime value before the sound channel stops playback.
		 * @return   A SoundChannel object, which you use to control the sound. This method returns null if you have no sound card or if you run out of available sound channels. The maximum number of sound channels available at once is 32.
		 */
		public function play(startTime:Number = 0, loops:int = 0):SoundChannel{
			__loops = loops;
			__isPlaying = true;
			if(channel) channel.removeEventListener(Event.SOUND_COMPLETE, soundComplete);
			__channel = sound.play(startTime, loops);
			setVolume();
			channel.addEventListener(Event.SOUND_COMPLETE, soundComplete, false, 0, true);
			
			return channel;
		}
		
		/**
		 * Stops the sound and returns the playhead back to zero.
		 */
		public function stop():void{
			channel.stop();
			__isPlaying = false;
		}
		
		/**
		 * Closes the stream, causing any download of data to cease. No data may be read from the stream after the close() method is called.
		 */
		public function close():void{
			sound.close();
			__isPlaying = false;
		}
		
		/**
		 * Plays the sound if it's paused, or pauses the sound if it's playing.
		 */
		public function playPause():void{
			if(!__isPaused){
				__pausePosition = channel.position;
				stop();
			} else {
				play(pausePosition, __loops);
			}
			__isPaused = !__isPaused;
		}
		
		//GETTERS & SETTERS
		/**
		 * The <code>Sound</code> object being decorated.
		 */
		public function get sound():Sound{
			return __sound;
		}
		
		/**
		 * @private
		 */
		public function set sound(value:Sound):void{
			__sound = value;
		}
		
		/**
		 * The <code>SoundChannel</code> object that the sound is using.
		 */
		public function get channel():SoundChannel{
			return __channel;
		}
		
		/**
		 * Volume of the sound.
		 */
		public function get vol():Number{
			return __vol;
		}
		
		/**
		 * @private
		 */
		public function set vol(value:Number):void{
			__vol = value;
			setVolume();
		}
		
		/**
		 *The time position the sound was paused in milliseconds.
		 */
		public function get pausePosition():Number{
			return __pausePosition;
		}
		
		/**
		 * Reference to the sound transform object that the sound is using.
		 */
		public function get soundTransform():SoundTransform{
			return __soundTransform;
		}
		
		/**
		 * @private
		 */
		public function set soundTransform(value:SoundTransform):void{
			__soundTransform = value;
		}
		
		/**
		 * The left-to-right panning of the sound, ranging from -1 (full pan left) to 1 (full pan right). A value of 0 represents no panning (balanced center between right and left).
		 */
		public function get pan():Number{
			return __pan;
		}
		
		/**
		 * @private
		 */
		public function set pan(value:Number):void{
			__pan = value;
			setPan();
		}
		
		/**
		 * Returns the currently available number of bytes in the sound object. This is usually only useful for externally loaded files.
		 */
		public function get bytesLoaded():Number{
			return sound.bytesLoaded;
		}
		
		/**
		 * Returns the total number of bytes in the sound object.
		 */
		public function get bytesTotal():Number{
			return sound.bytesTotal;
		}
		
		/**
		 * Provides access to the metadata that is part of an MP3 file.
		 * @see Sound.id3
		 */
		public function get id3():ID3Info{
			return sound.id3;
		}
		
		/**
		 * The current amplitude (vol) of the left channel, from 0 (silent) to 1 (full amplitude).
		 */
		public function get leftPeak():Number{
			return channel.leftPeak;
		}
		
		/**
		 * The current amplitude (vol) of the right channel, from 0 (silent) to 1 (full amplitude).
		 */
		public function get rightPeak():Number{
			return channel.rightPeak;
		}
		
		/**
		 * Returns <code>true</code> if the sound is currently playing
		 */
		public function get isPlaying():Boolean{
			return __isPlaying;
		}
		
		/**
		 * Returns <code>true</code> if the sound is currently paused
		 */
		public function get isPaused():Boolean{
			return __isPaused;
		}
		
		/**
		 * Current position of the sound in milliseconds
		 */
		public function get position():Number{
			return channel.position;
		}
		
		/**
		 * Returns the length of the sound in milliseconds. Unlike the regular <code>Sound.length</code> property, this returns the full length of the sound even if the sound has not fully loaded.
		 * To access the regular <code>length</code> property of the sound use <code>SoundDecorator.sound.length</code>
		 */
		public function get length():Number{
			var percentLoaded:Number = bytesLoaded/bytesTotal;
			var actualLength:Number = sound.length/percentLoaded;
			return actualLength;
		}
		
	}
	
}